Skip to content

ci(claude): allow fork-PR authors to trigger Claude review#932

Merged
kovtcharov-amd merged 1 commit into
mainfrom
ci/claude-allow-fork-prs
May 7, 2026
Merged

ci(claude): allow fork-PR authors to trigger Claude review#932
kovtcharov-amd merged 1 commit into
mainfrom
ci/claude-allow-fork-prs

Conversation

@kovtcharov

@kovtcharov kovtcharov commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

The claude-code-action gates execution on the actor's repo permission. Fork-PR authors only have read, so the action exits with "Actor does not have write permissions to the repository" before ever calling Anthropic — which is why PR #924 from @theonlychant (and any future fork PR) gets a 5-second red ❌ on the Claude AI Assistant check. Setting allowed_non_write_users: "*" on pr-review and issue-handler bypasses that gate so external contributors get the same auto-review maintainers do, with no username allowlist to maintain.

Threads

  • pr-review + issue-handler get allowed_non_write_users: "*" — these are the two jobs that take input from non-maintainers (fork-PR diffs, @claude mentions on issues / PR conversations).
  • pr-comment and release-notes left alonepr-comment is already filtered to non-fork PRs (head.repo.full_name == github.repository), and release-notes only fires from a maintainer-owned tag push via workflow_run. The actor on those is always a write user, so the gate is a no-op there.
  • Header comment documents the residual risk — fork-PR diff feeds into Claude, which has Bash in --allowedTools and access to ANTHROPIC_API_KEY + GITHUB_TOKEN. Prompt injection in the diff could try to coerce Claude into running an exfiltration command. The action's built-in mitigations (subprocess secret scrubbing via CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 auto-set when this input is non-empty, pinned bun binary, hardened PATH) reduce — but don't eliminate — the surface. Future-us reading the header knows what to tighten if a real attempt lands (swap * for a literal username list — verified upstream in src/github/validation/permissions.ts that allowed_non_write_users only takes literal usernames or *, not author_association values like CONTRIBUTOR).

Test plan

pull_request_target uses the workflow from main, not the PR head, so this PR can't validate itself. The verification path is post-merge:

  • Merge to main
  • Re-trigger PR feat(agents): split ChatAgent into Chat, FileIO, and DocumentQA agent… #924 (push a commit to it, or close/reopen) and confirm the Claude AI Assistant check now runs to completion instead of failing in 5s with the permission error
  • Confirm the run log shows ⚠️ SECURITY WARNING: Bypassing write permission check for theonlychant due to allowed_non_write_users configuration (the action emits this when the bypass takes effect)
  • Sanity-check that pr-comment (non-fork-only) still works on a maintainer's @claude review-comment reply

Closes #1053

The claude-code-action gates execution on the actor's repo permission.
Fork PR authors only have read, so the action exits with "Actor does
not have write permissions to the repository" before ever calling
Anthropic. Setting allowed_non_write_users: "*" on pr-review and
issue-handler bypasses that gate so external contributors get the same
auto-review maintainers do, without having to maintain a username
allowlist.

Skipped pr-comment (already filtered to non-fork PRs) and release-notes
(only fires from a maintainer-owned tag push). Header comment documents
the residual prompt-injection risk and the action's built-in mitigations
(subprocess secret scrubbing, pinned bun, hardened PATH) so future-us
knows what to tighten if a real exfiltration attempt lands.
@github-actions github-actions Bot added the devops DevOps/infrastructure changes label Apr 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Summary

Surgical 3-hunk change adding allowed_non_write_users: "*" to the two jobs that take input from non-maintainers (pr-review via pull_request_target, issue-handler via issues / issue_comment), plus a 12-line header block documenting the residual prompt-injection risk and the escape hatch (swap * for a literal username list). The scoping is correct: pr-comment is already filtered to same-repo PRs (head.repo.full_name == github.repository, line 334) and release-notes runs only on maintainer-owned tag pushes via workflow_run — adding the bypass to either would be a no-op. The single most important thing for the author/reviewer to know: this does widen the attack surface in exchange for fork-PR ergonomics, and the mitigation story leans entirely on upstream (claude-code-action) defense-in-depth rather than anything in this repo.

Issues Found

🟡 Important — claude_args for pr-review / issue-handler includes Edit,Write against repo files (claude.yml:322, 438)

Both fork-reachable jobs ship the same toolset:

--allowedTools Edit,Read,Write,Grep,Glob,Bash

The prompts only ever ask Claude to write /tmp/review.md / /tmp/reply.md and post via gh, so Edit against repo files isn't load-bearing. With allowed_non_write_users: "*" now letting an external author trigger these, a successful prompt injection has Edit + Write + Bash against the checked-out base-branch tree. The header already names secret exfiltration as the headline risk; arbitrary file writes (e.g. dropping a payload into a workflow file the next maintainer-triggered run picks up) is a quieter variant worth closing.

This isn't blocking — the PR is correctly scoped and pull_request_target itself doesn't push the worktree anywhere — but if you want the bypass to be defensible long-term, the cheapest follow-up is to either:

  • Drop Edit from these two jobs' claude_args (review/reply doesn't need it), or
  • Add a --disallowedTools line restricting Bash patterns (git push, curl, wget, nc, etc.) for the fork-reachable surface.

Worth a follow-up issue rather than blocking this PR, since the trade-off is documented and reverting is one config flip.

🟢 Minor — Header reference to src/github/validation/permissions.ts is ambiguous (claude.yml:39)

// no `author_association` values supported — checked
// upstream `src/github/validation/permissions.ts`, only literal usernames or `*`

Reads as a path in this repo on first scan. A future maintainer trying to verify the claim will grep src/github/... and come up empty. Suggest naming the source repo + file explicitly:

# this to a literal username list (no `author_association` values supported — verified
# in upstream anthropics/claude-code-action `src/github/validation/permissions.ts`,
# which only accepts literal usernames or `*`).

🟢 Minor — Test plan correctly notes self-test isn't possible, but the verification log line is worth pinning

The PR description references the upstream warning string ⚠️ SECURITY WARNING: Bypassing write permission check for <user> due to allowed_non_write_users configuration. That string is upstream-controlled and could change between SHAs. Once verified post-merge, consider adding it to the file header as the canonical signal so future-us doesn't re-derive it. Optional, not blocking.

Strengths

  • Header comment is genuinely useful, not just performative. Names the threat (prompt-injection in fork diff → Bash exfiltration via ANTHROPIC_API_KEY / GITHUB_TOKEN), the mitigations actually in play (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, pinned bun, hardened PATH), and the explicit escape hatch (literal username list). This is the rare "we accepted residual risk" comment that future-us can actually act on.
  • Scoping is right. Touching only pr-review and issue-handler — and explicitly not pr-comment (head.repo.full_name == github.repository at line 334 makes the bypass moot) or release-notes (workflow_run from maintainer tag push, actor is always a write user) — shows the author traced each job's trigger surface rather than carpet-bombing the file.
  • Test plan honestly acknowledges the pull_request_target self-test gap. The post-merge verification path (re-trigger PR feat(agents): split ChatAgent into Chat, FileIO, and DocumentQA agent… #924, confirm bypass log line) is concrete and falsifiable.

Verdict

Approve with suggestions — change is minimal, scoped correctly, and the security trade-off is documented in the right place (the workflow header, not a buried PR comment). The 🟡 on claude_args toolset is a hardening follow-up, not a blocker; the 🟢 items are textual. Safe to merge once the upstream-path clarification on line 39 is folded in.

@kovtcharov-amd kovtcharov-amd added this pull request to the merge queue May 7, 2026
Merged via the queue into main with commit 577436a May 7, 2026
19 checks passed
@kovtcharov-amd kovtcharov-amd deleted the ci/claude-allow-fork-prs branch May 7, 2026 21:49
@itomek itomek mentioned this pull request May 14, 2026
6 tasks
pull Bot pushed a commit to bhardwajRahul/gaia that referenced this pull request May 15, 2026
## Why this matters

v0.18.0 ships agent memory v2 (hybrid-search second brain with LLM
extraction and observability dashboard), ChatAgent split into three
composable agents (Chat/FileIO/DocumentQA), parallel tool calls, and a
Telegram adapter scaffold — plus fixes the RAG-on-PDF timeout with Gemma
4 that broke document Q&A since v0.17.6 and adds CI gates that enforce
RAG quality baselines on every future PR.

Full notes: `docs/releases/v0.18.0.mdx`.

## What's New

- **Agent memory v2** ([amd#606](amd#606)) —
Hybrid semantic + keyword search, LLM extraction, observability
dashboard via SSE streaming
([amd#1032](amd#1032)). Per-user isolation
enforced; extraction runs async so it doesn't add latency.
- **ChatAgent split** ([amd#979](amd#979)) —
`ChatAgent`, `FileIOAgent`, and `DocumentQAAgent` replace the monolithic
class; each composable via `tools=`. Backward-compatible shim preserved.
- **Parallel tool calls** ([amd#946](amd#946))
— Multiple `tool_calls` from a single LLM turn are executed
concurrently, cutting round-trips for multi-tool workflows.
- **Telegram adapter scaffold, Phase 0**
([amd#951](amd#951)) — `gaia telegram
start|stop|status`, per-user session isolation, `[telegram]` extras.
Phase 1 (message handling + allowed-users gate) tracked in
[amd#889](amd#889).
- **Connectors: per-MCP toggle + single-writer enforcement**
([amd#1018](amd#1018),
[amd#998](amd#998)) — Disable individual MCP
servers without removing them; concurrent writes serialised with
actionable errors on contention.
- **File navigation, web browsing, and write security**
([amd#495](amd#495)) — `FileSearchToolsMixin`,
web browsing tool, and scratchpad mixin in `KNOWN_TOOLS`; write tools
check `allowed_paths` before dispatch.
- **Email UI and policy alerts**
([amd#995](amd#995),
[amd#1039](amd#1039),
[amd#952](amd#952)) — Pre-scan triage card,
in-chat Connect, policy alert cards, and durable receipts for
confirmation-gated actions.

## Bug Fixes

- **RAG-on-PDF timeouts on Gemma 4**
([amd#1034](amd#1034), closes
[amd#1030](amd#1030)) — Prompt-size budget
check added at composition time; CI gates enforce it on every PR
([amd#1040](amd#1040)).
- **Envelope-level parse failure crashed SD recovery**
([amd#1047](amd#1047), closes
[amd#1023](amd#1023)) — Falls through to a
clean recovery path with step-1 context preserved.
- **Windows-path tool args corrupted**
([amd#1027](amd#1027)) — Backslash
normalisation now happens after argument parsing.
- **Blender `send_command` hung**
([amd#1026](amd#1026), closes
[amd#1022](amd#1022)) — Read timeout applied
to persistent-connection servers.
- **`gaia chat init` in post-install banner**
([amd#1029](amd#1029), closes
[amd#1024](amd#1024)) — Replaced with the
correct `gaia init`.
- **Keyring treated as required**
([amd#1028](amd#1028)) — Import guarded;
optional on systems without `keyring`.
- **electron-builder URLs stale**
([amd#953](amd#953)) — Three doc/installer
files updated to current download paths.

## Tooling & Docs

- **RAG eval CI gates** ([amd#1040](amd#1040),
closes [amd#1033](amd#1033)) — RAG quality
baselines + prompt-size budget enforced on every PR.
- **Fork-PR authors now receive Claude review**
([amd#932](amd#932)) —
`allowed_non_write_users: "*"` with prompt-injection mitigations
documented.
- **Eval runs mandated before merging**
([amd#1036](amd#1036)) — `CLAUDE.md` requires
`gaia eval agent` for LLM-affecting changes.
- **GAIA website** ([amd#369](amd#369)) —
[amd-gaia.ai](https://amd-gaia.ai) live.
- **Custom agent guide reorganised**
([amd#997](amd#997)), Lemonade PPA docs
([amd#801](amd#801)), broken Lemonade CLI URL
fixed ([amd#996](amd#996)), WhatsApp adapter
evaluation spec ([amd#950](amd#950)).

## Release checklist

- [x] `util/validate_release_notes.py docs/releases/v0.18.0.mdx --tag
v0.18.0` passes
- [x] `src/gaia/version.py` → `0.18.0`
- [x] `src/gaia/apps/webui/package.json` → `0.18.0`
- [x] Navbar label in `docs/docs.json` → `v0.18.0 · Lemonade 10.2.0`
- [x] All 28 commits in range (v0.17.6..HEAD) are represented in the
notes
- [ ] Review from @kovtcharov-amd addressed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devops DevOps/infrastructure changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(claude): fork-PR authors blocked from Claude AI Assistant auto-review

3 participants